;-------------------------------------------------------------------------------;
;									        ;
;	  SpeechGlue -  Interface for PASCAL programs using the		        ;
;		        Macintalk speech driver.			        ;
;									        ;
;	  Copyright 1984, Joseph Katz / Mark Barton.  Not to be copied or       ;
;	  distributed without the express written permission of the authors.    ;
;									        ;
;	  Modification History:						        ;
;	       06/02/85 - Modified only minutes before shipment of software     ;
;			  supplement.  Modified to separate code and data       ;
;			  segments.  This was done in a vast hurry.  There      ;
;			  are no known bugs in it, but it was not done cleanly. ;
;									        ;
;									        ;
;-------------------------------------------------------------------------------;

          .NOLIST
          .INCLUDE   TLASM/SYSEQU.TEXT
          .INCLUDE   TLASM/SYSTRAPS.TEXT
          .INCLUDE   TLASM/SYSERR.TEXT
          .INCLUDE   TLASM/TOOLTRAPS.TEXT
          .INCLUDE   TLASM/TOOLEQU.TEXT
          .LIST


RdrHandle    .EQU   52	      ;PBLOCK OFFSET OF HANDLE TO READER CODE
RulesFN	     .EQU   60	      ;OFFSET TO READER EXCEPTIONS FILENAME POINTER

IDPitchMode  .EQU   0
IDRate	     .EQU   2	      ;ID=1 IS USED FOR KILLIO
IDSex	     .EQU   3	      ;RESERVED FOR FUTURE IMPLEMENTATION
IDBaseline   .EQU   4

fullUnitT    .EQU   -4000     ;ERROR CODE FOR DRIVER TABLE FULL

Pblock	     .EQU   0	      ;OFFSET TO IOPARMBLOCK
ReaderHandle .EQU   66	      ;OFFSET TO READER HANDLE
SpeechRfNum  .EQU   70	      ;OFFSET TO SPEECH DRIVER REFERENCE NUMBER
TalkRefNum   .EQU   74	      ;OFFSET TO TALK PACKAGE REFNUM


; FUNCTION SpeechOn(EXCPFile: Str255; VAR SpeechHandle: SpeechRecord): SpeechErr;

; Open speech driver. This is complicated by the need to arbitrate the
; driver unit number, which must be done prior to the actual device open.

          .FUNC	    SpeechOn,1

          MOVE.L    A2,-(SP)	        ;SAVE A2 ON STACK

; Try to allocate driver globals area
          MOVE.L    #100,D0	        ;GRAB 100 BYTES
          _NewHandle
          MOVE.L    8(SP),A1	        ;GET SPEECH RECORD ADDR
          MOVE.L    A0,(A1)	        ;SAVE GLOBALS HANDLE
          TST.W	    D0
          BNE.S	    ONERROR	        ;IF ERROR, JUST RETURN
          _HLock		        ;LOCK HANDLE
          MOVE.L    (A0),A2	        ;SAVE POINTER TO GLOBALS

; Clear parm block
          MOVEQ	    #0,D0
          MOVEQ	    #24,D1
          MOVE.L    A2,A0
CLOOP	  MOVE.L    D0,(A0)+
          DBF	    D1,CLOOP

; Open MacinTalk resource file

          CLR.W	    -(SP)	        ;SPACE FOR RETURN CODE
          PEA	    DriverID	        ;NAME OF RESOURCE FILE
          _OpenResFile		        ;OPEN IT
          MOVE.W    (SP)+,D0	        ;GET RESOURCE FILE ID
          BPL.S	    GOTRESFILE	        ;GOOD RETURN, BRANCH
          MOVE.W    ResErr,D0	        ;SET RETURN CODE

ONERROR	  MOVE.L    (SP)+,A2	        ;RESTORE A2
          MOVE.L    (SP),8(SP)	        ;MOVE RETURN ADDR
          ADDQ.L    #8,SP	        ;FIX STACK
          MOVE.W    D0,4(SP)	        ;SET RETURN CODE
          RTS			        ;AND GO AWAY

GOTRESFILE MOVE.W   D0,TalkRefNum(A2)   ;SAVE FILE REFNUM FOR LATER


; Set ResPreLoad to FALSE

          MOVE.B    #0,-(SP)
          _SetResLoad


; Get handle to synthesizer resource; TYPE = DRVR, NAME=MacinTalk

          CLR.L	    -(SP)	        ;SPACE FOR RETURN CODE
          MOVE.L    #'DRVR',-(SP)       ;RESOURCE TYPE
          PEA	    SpeechName	        ;RESOURCE NAME
          _GetNamedResource	        ;GET DRIVER RESOURCE
          MOVE.L    (SP)+,A1	        ;GET HANDLE TO RESOURCE (*** WAS AFTER BNE)
          MOVE.W    ResErr,D0	        ;GOOD RETURN?
          BNE.S	    ONERROR	        ;NO, BRANCH TO ERROR RETURN


; Restore ResPreLoad to TRUE

          MOVE.B    #1,-(SP)
          _SetResLoad


; Find an empty slot in driver unit table (looking backwards from end)

          MOVE.L    uTableBase,A0       ;GET DRIVER UNIT TABLE BASE
          ADDA.W    #128,A0	        ;START AT END+4 OF UNIT TABLE
          MOVE.W    #32,D0	        ;LAST UNIT IN TABLE IS #31

@3	  SUBQ.W    #1,D0	        ;REDUCE SLOT NUMBER
          CMP.W	    #18,D0	        ;NO FREE SLOTS BELOW UNIT 18
          BGT.S	    @4
          MOVE.W    #fullUnitT,D0       ;SETUP ERROR CODE
          BRA.S	    ONERROR	        ;AND GO AWAY

@4	  CMPI.L    #0,-(A0)	        ;IS SLOT EMPTY?
          BNE.S	    @3		        ;NO, KEEP LOOPING


; Change resource ID to new driver number, and load in resource data

          MOVE.W    D0,D1	        ;DRIVER NUMBER
          ADDQ.W    #1,D1	        ;
          NEG.W	    D1		        ;REF NUMBER
          MOVE.W    D1,SpeechRfNum(A2)  ;SAVE REFERENCE NUMBER

          MOVE.L    A1,-(SP)	        ;RESOURCE HANDLE
          MOVE.W    D0,-(SP)	        ;NEW DRIVER NUMBER
          MOVE.L    #0,-(SP)	        ;DON'T CHANGE THE RESOURCE NAME
          _SetResInfo


          MOVE.L    A1,-(SP)
          _LoadResource
          MOVE.W    resErr,D0	        ;FOR DEBUGGING


          MOVE.L    A1,A0	        ;TRANSFER HANDLE TO A0
          _HLock		        ;LOCK DRIVER CODE DOWN


; Get SpeechOn input parms from stack

          MOVE.L    (SP)+,A2	        ;RESTORE A2
          MOVE.L    (SP)+,A0	        ;GET RETURN ADDRESS
          MOVE.L    (SP)+,D0	        ;GET ADDR OF SPEECHREC HANDLE
          MOVE.L    (SP)+,A1	        ;GET EXCPS FILENAME PTR
          MOVE.L    A0,-(SP)	        ;RESTORE RETURN ADDR

          MOVE.L    A2,-(SP)	        ;SAVE A2
          MOVE.L    D0,A2	        ;ADDR OF DEV GLOBALS HANDLE
          MOVE.L    (A2),A2	        ;DEVICE GLOBALS HANDLE
          MOVE.L    A2,-(SP)	        ;SAVE DEVICE GLOBALS HANDLE
          MOVE.L    (A2),A2	        ;DEREF TO POINTER

; Build parm block and open the driver

          LEA	    Pblock(A2),A0       ;A0 --> PARM BLOCK
          MOVE.L    A1,RulesFN(A0)      ;SAVE PTR TO EXCPS FILENAME IN PBLOCK
          LEA	    SpeechName,A1       ;A1 --> DRIVER NAME
          MOVE.L    A1,IOFileName(A0)   ;STORE DRIVER NAME IN PARM BLOCK
          CLR.W	    IODrvNum(A0)        ;USE DEFAULT DRIVE
          CLR.W	    IOFileType(A0)      ;CLEAR TYPE/PERMISSIONS
          CLR.L	    IOOwnBuf(A0)        ;USE SYSTEM BUFFER

          _Open
          MOVE.W    IOResult(A0),12(SP)		    ;SAVE RETURN CODE FROM OPEN
          MOVE.L    RdrHandle(A0),ReaderHandle(A2)  ;SAVE HANDLE TO READER CODE

          MOVE.L    (SP)+,A0	        ;UNLOCK DEVICE GLOBALS
          _HUnlock

          MOVE.L    (SP)+,A2	        ;RESTORE A2
          RTS


SpeechName .BYTE    7
           .ASCII   '.SPEECH'
DriverID   .BYTE    9
           .ASCII   'MacinTalk'




;  PROCEDURE SpeechOff(SpeechHandle: SpeechRecord);

;	  Closes speech driver.

          .PROC	    SpeechOff

          MOVE.L    A2,-(SP)	        ;SAVE A2
          MOVE.L    8(SP),A0	        ;GET DEVICE GLOBALS HANDLE
          _HLock
          MOVE.L    (A0),A2	        ;DEREFERENCE TO POINTER

          LEA	    Pblock(A2),A0       ;POINTER TO PARM BLOCK
          _Close    ,IMMED

          MOVE.W    TalkRefNum(A2),D0   ;MacinTalk REFNUM
          BEQ.S	    @1		        ;DONT CLOSE SYSTEM RES FILE
          MOVE.W    D0,-(SP)
          _CloseResFile		        ;BYE-BYE

@1	  MOVE.L    8(SP),A0	        ;GET DEVICE GLOBALS HANDLE
          _HUnlock		        ;UNLOCK IT
          _DisposHandle		        ;TOSS IT
          MOVE.L    (SP)+,A2	        ;RESTORE A2
          MOVE.L    (SP),4(SP)	        ;FIXUP STACK AND RETURN
          ADDQ.L    #4,SP
          RTS


;  PROCEDURE SpeechRate(SpeechHandle: SpeechRecord; Rate: Integer);

;	  Set the speaking rate in words/minute.  Range and validity
;	  checking is done in SpeechPrime.

          .PROC	    SpeechRate

          MOVE.L    (SP)+,D1	        ;GET RETURN ADDR
          MOVE.W    (SP)+,D2	        ;GET RATE PARM
          MOVE.L    (SP)+,A0	        ;GET DEVICE GLOBALS
          MOVE.L    D1,-(SP)	        ;RESTORE RETURN ADDR

          MOVE.L    A0,-(SP)	        ;SAVE DEVICE GLOBALS HANDLE
          _HLock		        ;LOCK IT
          MOVE.L    (A0),A1	        ;DEREF GLOBALS HANDLE

          LINK	    A6,#-32			  ;CREATE PARM BLOCK ON STACK
          LEA	    -32(A6),A0			  ;POINT A0 AT PARM BLOCK

          MOVE.W    SpeechRfNum(A1),IORefNum(A0)  ;SET SPEECH REFNUM
          MOVE.W    #IDRate,CSCode(A0)		  ;SET RATE PARM ID
          MOVE.W    D2,CSParam(A0)		  ;STORE RATE PARM

          _Control  ,IMMED

          UNLK	    A6			        ;REMOVE DCE FROM STACK

          MOVE.L    (SP)+,A0	        ;RESTORE DEVICE GLOBALS
          _HUnlock		        ;UNLOCK IT

          RTS



;  PROCEDURE SpeechSex(SpeechHandle: SpeechRecord; SpeakerSex: Sex);

;	  Set the speaking sex as Male or Female.

          .PROC	    SpeechSex

          MOVEQ	    #0,D2	        ;CLEAR
          MOVE.L    (SP)+,D1	        ;GET RETURN ADDR
          MOVE.B    (SP)+,D2	        ;GET SEX PARM (NOTE .B ON MOVE)
          MOVE.L    (SP)+,A0	        ;GET DEVICE GLOBALS
          MOVE.L    D1,-(SP)	        ;RESTORE RETURN ADDR

          MOVE.L    A0,-(SP)	        ;SAVE GLOBALS ON STACK
          _HLock		        ;LOCK DEVICE GLOBALS
          MOVE.L    (A0),A1	        ;DEREFERENCE TO POINTER

          LINK	    A6,#-32			  ;CREATE PARM BLOCK ON STACK
          LEA	    -32(A6),A0			  ;POINT A0 AT PARM BLOCK
          MOVE.W    SpeechRfNum(A1),IORefNum(A0)  ;SET SPEECH REFNUM
          MOVE.W    #IDSex,CSCode(A0)		  ;SET SEX PARM ID
          MOVE.W    D2,CSParam(A0)		  ;STORE SEX PARM

          _Control  ,IMMED

          UNLK	    A6		        ;REMOVE PARM BLOCK FROM STACK

          MOVE.L    (SP)+,A0	        ;RESTORE GLOBALS HANDLE
          _HUnlock		        ;UNLOCK HANDLE

          RTS



;  PROCEDURE SpeechPitch(SpeechHandle: SpeechRecord; Pitch: Integer; Mode: F0Mode);

;	  Sets the baseline pitch and the F0 mode.  All parameter
;	  validity and range checking is done in SpeechPrime routines.

          .PROC	    SpeechPitch

          MOVEQ	    #0,D2	        ;CLEAR THE BUGGER
          MOVE.L    (SP)+,A1	        ;GET RETURN ADDR
          MOVE.B    (SP)+,D2	        ;GET PITCH MODE
          MOVE.W    (SP)+,D1	        ;GET BASELINE PITCH
          MOVE.L    (SP)+,A0	        ;GET DEVICE GLOBALS HANDLE
          MOVE.L    A1,-(SP)	        ;RESTORE RETURN ADDR

          MOVE.L    A0,-(SP)	        ;SAVE GLOBALS ON STACK
          _HLock		        ;LOCK GLOBALS
          MOVE.L    (A0),A1	        ;DEREFERENCE

          LINK	    A6,#-32	        ;CREATE ROOM FOR PARM BLOCK
          LEA	    -32(A6),A0
          MOVE.W    SpeechRfNum(A1),IORefNum(A0)

          MOVE.W    #IDPitchMode,CSCode(A0)	  ;SET MODE ID
          MOVE.W    D2,CSParam(A0)		  ;SET PITCH MODE
          _Control  ,IMMED

          MOVE.W    #IDBaseline,CSCode(A0)	  ;SET BASELINE ID
          MOVE.W    D1,CSPARAM(A0)		  ;SET BASELINE PITCH
          _Control  ,IMMED

          UNLK	    A6

          MOVE.L    (SP)+,A0	        ;RESTORE GLOBALS HANDLE
          _HUnlock		        ;UNLOCK HANDLE

          RTS



;  FUNCTION MacinTalk(SpeechHandle: SpeechRecord; phonemes: Handle): SpeechErr;

;	  Macintalk glue passes phonemes handle to speech driver
;	  write routine.

          .FUNC	    MacinTalk

          MOVE.L    (SP)+,D0	        ;GET RETURN ADDR
          MOVE.L    (SP)+,A1	        ;GET INPUT HANDLE
          MOVE.L    (SP)+,A0	        ;GET DEVICE GLOBALS HANDLE
          MOVE.L    D0,-(SP)	        ;RESTORE RETURN ADDR

          MOVE.L    A0,-(SP)	        ;SAVE ON STACK
          _HLock		        ;LOCK GLOBALS HANDLE

          MOVE.L    A1,A0	        ;COPY INPUT HANDLE TO A0
          _GetHandleSize	        ;GET INPUT HANDLE SIZE IN D0

          MOVE.L    A2,-(SP)
          MOVE.L    4(SP),A2
          MOVE.L    (A2),A2

          LEA	    Pblock(A2),A0		  ;POINT A0 TO PARM BLOCK
          MOVE.W    SpeechRfNum(A2),IORefNum(A0)  ;STORE REF NUM
          MOVE.L    A1,IOBuffer(A0)		  ;STORE INPUT HANDLE
          MOVE.L    D0,IOByteCount(A0)		  ;STORE INPUT HANDLE LENGTH

          _Write    ,IMMED	        ;GO TO IT

          MOVE.L    (SP)+,A2	        ;RESTORE A2
          MOVE.L    (SP)+,A0	        ;RESTORE DEVICE GLOBALS HANDLE
          MOVE.W    D0,-(SP)	        ;SAVE WRITE RETURN CODE
          _HUnlock		        ;UNLOCK GLOBALS
          MOVE.W    (SP)+,D0	        ;RESTORE WRITE RETURN CODE

          MOVE.W    D0,4(SP)	        ;PUT RETURN CODE ON STACK
          RTS



; FUNCTION Reader(theSpeech: SpeechHandle;
;		  English: Pointer;
;		  EnglishLen: LongInt;
;		  Output: Handle): Integer;


          .FUNC	    Reader,2	        ;READER GLUE

          MOVE.L    16(SP),A0	        ;GET DEVICE GLOBALS HANDLE
          MOVE.L    12(SP),16(SP)
          MOVE.L    8(SP),12(SP)
          MOVE.L    4(SP),8(SP)
          MOVE.L    (SP),4(SP)
          ADDQ.L    #4,SP

          _HLock
          MOVE.L    (A0),A0			  ;DEREFERENCE GH
          MOVE.L    ReaderHandle(A0),A0		  ;GET HANDLE TO READER CODE
          _HLock				  ;LOCK CODE DOWN

          MOVE.L    (A0),A0	        ;GET PTR TO CODE
          JMP	    (A0)	        ;GO DO IT

          RTS

          .END

